package com.sojoys.artifact.plugin.netty.web;

import com.jfinal.plugin.IPlugin;
import com.sojoys.artifact.constant.ConfigConstant;
import com.sojoys.artifact.core.IRouter;
import com.sojoys.artifact.core.IWebService;
import com.sojoys.artifact.manager.PropManager;
import com.sojoys.artifact.third.web.CiReload;
import com.sojoys.artifact.third.web.NettyHttpConfig;
import com.sojoys.artifact.third.web.codec.WebChannelIniter;
import com.sojoys.artifact.third.web.core.NettyHttpContext;
import com.sojoys.artifact.third.web.handler.NettyHttpHandler;
import com.sojoys.artifact.third.web.handler.NettyHttpWebSocketHandler;
import com.sojoys.artifact.third.web.router.CiRouter;
import com.sojoys.artifact.third.web.router.CiWebSocketHandler;
import com.xiaoleilu.hutool.util.ClassUtil;
import com.xiaoleilu.hutool.util.ReflectUtil;
import com.xiaoleilu.hutool.util.StrUtil;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.multipart.DiskAttribute;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.ssl.SslContext;
import io.netty.util.concurrent.DefaultEventExecutor;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * ci-服务中心
 * @author zhh
 */
public class NettyWebPlugin implements IWebService,IPlugin {

    private static InternalLogger logger = InternalLoggerFactory.getInstance(NettyWebPlugin.class);
    
    protected NioEventLoopGroup bossGroup;
    protected NioEventLoopGroup workerGroup;
    // 业务线程池
 	EventExecutorGroup eventExecutorGroup=null;
 	
    protected ServerBootstrap boot;
    protected ChannelFuture bossChannel;
    
    protected NettyHttpHandler handler;
    protected final CiWebSocketHandlerProxy wsHandlerProxy;
    
    protected CiRouter router;
    
    protected final SslContext ssl;
    
    private int port = 0;
    private int web_work_thread = 0;
    private boolean webSocket = false;
    private String webSocketPath = "";
    private int limit = 0;
    private int web_max_msg_size = 0;
    private String web_static_dir = "";
    private String web_file_router = "";
    private String scan_controller = "";
    private String scan_dir = "";
    private int fileMaxAge = 0;
    private String web_except_extend = "";
    private String web_alow_hosts = "web.alow.hosts";
    private String web_cors_conf = "web.cors.conf";
    private String web_filter = "web.filter";
    //构造代码块
    {
    	port = PropManager.getInt(ConfigConstant.HTTP_PORT,8080);
    	web_work_thread = PropManager.getInt(ConfigConstant.WEB_WORK_THREAD,4);
    	webSocket = PropManager.getBoolean(ConfigConstant.WEB_SOCKET,false);
    	webSocketPath = PropManager.get(ConfigConstant.WEB_SOCKET_PATH,"/websocket");
    	limit = PropManager.getInt(ConfigConstant.WEB_MAX_LIMIT,1000);
    	web_max_msg_size = PropManager.getInt(ConfigConstant.WEB_MAX_MSG_SIZE,64*1024);
    	web_static_dir = PropManager.get(ConfigConstant.WEB_STATIC_DIR,"/static_file");
    	scan_controller = PropManager.get(ConfigConstant.WEB_SCAN_CONTROLLER);
    	fileMaxAge = PropManager.getInt(ConfigConstant.WEB_FILE_MAXAGE,604800);
    	web_file_router = PropManager.get(ConfigConstant.WEB_FILE_ROUTER,"/");
    	web_except_extend = PropManager.get(ConfigConstant.WEB_EXCEPT_EXTEND,"");
    	web_alow_hosts = PropManager.get(ConfigConstant.WEB_ALOW_HOSTS);
    	web_cors_conf = PropManager.get(ConfigConstant.WEB_CORS_CONF,"*");
    	scan_dir = PropManager.get(ConfigConstant.WEB_SCAN_DIR,ClassUtil.getClassPath());
    	web_filter = PropManager.get(ConfigConstant.WEB_FILTER,"");
    }
    
    public NettyWebPlugin(SslContext sslContext) {
        router = new CiRouter();
        this.wsHandlerProxy = new CiWebSocketHandlerProxy();
        this.ssl = sslContext;
        if (!StrUtil.isBlank(web_filter)) {
        	IRouter filter = ReflectUtil.newInstance(web_filter);
        	router.setFilter(filter);
		}
        DiskFileUpload.deleteOnExitTemporaryFile = false;
        DiskAttribute.deleteOnExitTemporaryFile = false;
    }
    
    public boolean start(){
        if(boot!=null){
            stop();
        }
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup();
        eventExecutorGroup = new DefaultEventExecutorGroup(web_work_thread);
        
        
        boot = new ServerBootstrap();
        
        boot.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_REUSEADDR, NettyHttpConfig.SocketReuseAddr)
        .childOption(ChannelOption.SO_KEEPALIVE, NettyHttpConfig.SocketKeepAlive)
        .option(ChannelOption.TCP_NODELAY, NettyHttpConfig.TCP_NODELAY)
        .option(ChannelOption.SO_BACKLOG, NettyHttpConfig.SocketBackLog)
        .option(ChannelOption.SO_RCVBUF, NettyHttpConfig.SocketRcvbufSize)
        .option(ChannelOption.SO_SNDBUF, NettyHttpConfig.SocketSndbufSize)
        .childHandler(channelIniter());
        
        bossChannel = boot.bind(port);
        try {
            bossChannel.sync();
            logger.info("Web server started at port:" + port + '.');
            logger.info("Open your browser and navigate to http"+(ssl==null?"":"s")+"://localhost:" + port + '/');
        } catch (InterruptedException e) {
        	return false;
        }
        reload();
        return true;
    }
    
    public boolean stop(){
        return true;
    }
    
    public void shutdown(){
    	if(boot!=null){
            handler.disable();
            long waitTm = Math.min(10000L, httpConnectNum()*2);
            try {
                Thread.sleep(waitTm);
            } catch (InterruptedException e) {
            }
            boot = null;
           
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
			eventExecutorGroup.shutdownGracefully();
            bossChannel.channel().close();
            dev(false);
        }
    }
    
    public boolean isAlive(){
        return boot!=null;
    }

    public void setWebSocketHandler(CiWebSocketHandler handler){
        wsHandlerProxy.setHandler(handler);
    }
    public CiWebSocketHandler getWebSocketHandler(){
        return wsHandlerProxy.getHandler();
    }
    public ChannelGroup webSocketGroup(){
        return wsHandlerProxy.channelGroup();
    }
    
    private CiReload reloader;
    public void dev(){
    	dev(true);
    }
    public synchronized void dev(boolean open){
    	if(reloader!=null){
            reloader.stop();
            reloader = null;
        }
    	if(open){
    		reloader = new CiReload(this, router.urls());
    		reloader.start();
    	}
    }
    
    public void reload() {
		logger.info("start reLoad handlers... ...");
        CiRouter r = new CiRouter();
        r.setHandler(scan_dir, scan_controller);
        r.setFile(web_static_dir, web_file_router, fileMaxAge, web_except_extend);
        if(router!=null){
            r.getHandler().add(router.getHandler().handlers());
        }
        if (!StrUtil.isBlank(web_filter)) {
        	IRouter filter = ReflectUtil.newInstance(web_filter);
        	r.setFilter(filter);
		}
        this.router = r;
		logger.info("end reLoad handlers");
    }

    public int httpConnectNum(){
        NettyHttpHandler h = handler;
        return h==null?0:h.connectNum();
    }
    
    private ChannelInitializer<?> channelIniter() {
        if(webSocket){
            handler = new NettyHttpWebSocketHandler(wsHandlerProxy, webSocketPath, limit) {
                @Override
                protected boolean doProcess(NettyHttpContext context) throws Exception {
                    return router.call(context);
                }
            };
        }else{
            handler = new NettyHttpHandler(limit) {
                @Override
                protected boolean doProcess(NettyHttpContext context) throws Exception {
                    return router.call(context);
                }
            };
        }
        WebChannelIniter initer = new WebChannelIniter(web_max_msg_size, web_alow_hosts, web_cors_conf, handler,eventExecutorGroup);
        initer.ssl = this.ssl;
        return initer;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    private class CiWebSocketHandlerProxy implements CiWebSocketHandler {

        private ChannelGroup group;
        private CiWebSocketHandler handler;
        private IWebService service;
        public CiWebSocketHandlerProxy(){
            group = new DefaultChannelGroup(new DefaultEventExecutor());
        }
        public CiWebSocketHandler getHandler(){
            return handler;
        }
        public void setHandler(CiWebSocketHandler h){
            this.handler = h;
            if(h!=null){
                this.handler.init(service);
            }
        }

        public ChannelGroup channelGroup(){
            return group;
        }
        
        public void init(IWebService service){
            this.service = service;
        }
        
        @Override
        public void onOpen(Channel channel) {
            group.add(channel);
            if(handler!=null){
                handler.onOpen(channel);
            }
        }

        @Override
        public void onText(Channel channel, String data) {
            if(handler!=null){
                handler.onText(channel, data);
            }
        }

        @Override
        public void onBytes(Channel channel, byte[] bytes) {
            if(handler!=null){
                handler.onBytes(channel, bytes);
            }
        }

        @Override
        public void onClose(Channel channel, boolean lost) {
            if(handler!=null){
                handler.onClose(channel, lost);
            }
        }

    }
}
